package com.gframework.mybatis.dao.datasource;

import java.io.Closeable;
import java.io.IOException;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;

import javax.sql.DataSource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;

import com.gframework.annotation.GcAfterUsed;

/**
 * 本类是一个 {@link DynamicDataSourceManager}接口的基本实现了.<br>
 * <p>
 * 本类只负责数据源管理，不负责初始化，任何地方都可以注入此bean进行数据源的初始化操作。
 * 同时本类也提供了重新获取，刷新数据源配置等功能。
 * </p>
 * 本类是线程安全的
 * 
 * @since 1.0.0
 * @author Ghwolf
 */
public class DynamicDataSourceManagerImpl implements DynamicDataSourceManager,DisposableBean {

	private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceManagerImpl.class);
	
	/**
	 * 数据源存储池
	 */
	private final Map<String, DataSourceProvider> dataSources = new ConcurrentHashMap<>();
	
	public DynamicDataSourceManagerImpl(){
		logger.info("====> 已启用 DynamicDataSourceManagerImpl 多数据源管理器！可以注入DynamicDataSourceManager接口bean进行多数据源管理操作！");
	}
	
	@Override
	public synchronized boolean register(String key, DataSource dataSource) {
		if (key == null || dataSource == null) throw new NullPointerException("key或dataSource不能为null！");

		
		if(logger.isWarnEnabled()) {
			Method m = this.getCloseMethod(dataSource);
			if (m == null) {
				// 如果没有关闭方法，则日志警告
				logger.warn("\n【警告】 - key=[{}] 的 [{}] 类的数据源对象没有close方法，这可能导致在关闭数据源的时候会造成无法回收内存泄漏的情况。请确保你的程序是否正确！", key,
						dataSource.getClass().getName());
			}
		}
		
		if (this.dataSources.containsKey(key)) {
			return false;
		}

		this.dataSources.put(key, new DataSourceObjectProvider(dataSource));
		return true;
	}
	

	@Override
	public synchronized boolean register(String key, Supplier<DataSource> lazyDataSource) {
		if (key == null || lazyDataSource == null) throw new NullPointerException("key或lazyDataSource不能为null！");

		if (this.dataSources.containsKey(key)) {
			return false;
		}

		this.dataSources.put(key, new DataSourceSupplierProvider(lazyDataSource));
		return true;
	}

	@Override
	public synchronized boolean remove(String key) {
		if (key == null) return false;
		DataSourceProvider dsp = this.dataSources.remove(key);
		if (dsp == null || !dsp.hasDataSource()) return false ;
		
		DataSource ds = dsp.getDataSource();
		
		if (ds instanceof Closeable) {
			try {
				((Closeable) ds).close();
			} catch (IOException e) {
				if (logger.isErrorEnabled()) {
					logger.error("关闭dataSource出错[{}],key={}",ds.getClass().getName(),key,e);
				}
			}
		} else {
			closeDataSourceByCloseMethod(key,ds);
		}
		
		if (logger.isDebugEnabled()) {
			logger.debug("删除了一个第三方数据库连接池：{}",key);
		}
		return true;
	}

	@Override
	public Connection openConnection(String key) throws SQLException {
		DataSourceProvider dsp = this.dataSources.get(key);
		if (dsp == null) {
			throw new SQLException("key=[{}]的数据源不存在！",key);
		}
		return dsp.getDataSource().getConnection();
	}
	
	@Override
	public Connection openConnection(String key, String username, String password) throws SQLException {
		DataSourceProvider dsp = this.dataSources.get(key);
		if (dsp == null) {
			throw new SQLException("key=[{}]的数据源不存在！",key);
		}
		return dsp.getDataSource().getConnection(username,password);
	}
	
	/**
	 * 本方法负责尝试给予close方法关闭数据源的操作
	 */
	private void closeDataSourceByCloseMethod(String key,DataSource ds) {
		Method m = this.getCloseMethod(ds);
		if (m == null) {
			if(logger.isWarnEnabled()) {
				logger.warn("\n【警告】 - key=[{}] 的 [{}] 类的数据源对象没有close方法，并且目前触发了关闭该数据源的操作，但未做任何事情！", key,ds.getClass().getName());
			}
		} else {
			try {
				m.invoke(ds);
			} catch (Exception e) {
				if(logger.isErrorEnabled()) {
					logger.error("尝试关闭key=[{}]的[{}]数据源对象出现错误！",key,ds.getClass().getName());
				}
			}
		}
	}
	
	/**
	 * 取得一个对象的关闭方法
	 * @param obj 要查询的对象
	 * @return 如果存在则返回Method对象，否则返回null
	 * @throws SecurityException 如果存在安全管理器禁止此反射操作
	 */
	private Method getCloseMethod(Object obj) {
		Class<?> cls = obj.getClass();
		while(cls != Object.class) {
			Method[] ms = cls.getDeclaredMethods();
			for (Method m : ms) {
				if (m.getName().equals("close") && m.getParameterCount() == 0) {
					m.setAccessible(true);
					return m;
				}
			}
			cls = cls.getSuperclass();
		}
		return null ;
	}

	@Override
	public boolean contains(String key) {
		return this.dataSources.containsKey(key);
	}

	@Override
	public Set<String> getCurrentKeys() {
		return this.dataSources.keySet();
	}
	
	private interface DataSourceProvider {
		public DataSource getDataSource();
		public boolean hasDataSource() ;
	}
	
	private class DataSourceObjectProvider implements DataSourceProvider{
		
		private final DataSource dataSource ;
		DataSourceObjectProvider(DataSource dataSource){
			this.dataSource = dataSource ;
		}

		@Override
		public DataSource getDataSource() {
			return dataSource;
		}

		@Override
		public boolean hasDataSource() {
			return true;
		}
	}
	
	private class DataSourceSupplierProvider implements DataSourceProvider{
		
		@GcAfterUsed
		private Supplier<DataSource> supplier ;
		private DataSource dataSource ;
		DataSourceSupplierProvider(Supplier<DataSource> supplier) {
			this.supplier = supplier ;
		}
		@Override
		public DataSource getDataSource() {
			if (dataSource == null) {
				synchronized(this){
					if (dataSource == null) {
						dataSource = supplier.get();
						supplier = null ;
					}
				}
			}
			return dataSource;
		}
		@Override
		public synchronized boolean hasDataSource() {
			return dataSource != null ;
		}
		
	}

	@Override
	public void destroy() throws Exception {
		dataSources.forEach((k,v)-> {
			if (v.hasDataSource()) {
				DataSource ds = v.getDataSource();
				if (ds instanceof Closeable) {
					try {
						((Closeable) ds).close();
					} catch (IOException e) {
						// Ignore
					}
				}
			}
		});
	}

}
